module;
#include <QObject>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QFile>
#include <QIODevice>
#include <QModbusRtuSerialClient>
#include <QModbusTcpClient>
#include <QSerialPort>
#include <QDebug>
#include <QtCore/qtmochelpers.h>
module XYmodbus;

ModbusClientHandle::ModbusClientHandle(QObject *parent)
    : QObject(parent), modbusDevice(nullptr), pendingReadRequests(0), pendingWriteRequests(0)
{
}

ModbusClientHandle::~ModbusClientHandle()
{
    modbusDevice->disconnectDevice();
    delete modbusDevice;
    modbusDevice=nullptr;
    pendingReadRequests=0;
    pendingWriteRequests=0;
}

bool ModbusClientHandle::loadConfig(const QString &configFilePath)
{
    QFile file(configFilePath);
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Cannot open config file:" << configFilePath;
        return false;
    }

    QByteArray jsonData = file.readAll();
    file.close();

    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
    if (jsonDoc.isNull() || jsonDoc.isObject() == false) {
        qWarning() << "Invalid JSON format in config file.";
        return false;
    }

    config = jsonDoc.object();

    // Load connection configuration
    connectionCfg.clear();
    QJsonObject connObj = config.value("Connection").toObject();
    connectionCfg["Type"] = connObj.value("Type").toString();
    connectionCfg["IP"] = connObj.value("IP").toString();
    connectionCfg["UnitID"] = connObj.value("UnitID").toInt();
    connectionCfg["Timeout"] = connObj.value("Timeout").toInt();
    connectionCfg["Retries"] = connObj.value("Retries").toInt();

    if (connectionCfg["Type"].toString() == "RTU") {
        connectionCfg["BaudRate"] = connObj.value("BaudRate").toInt();
        connectionCfg["Parity"] = connObj.value("Parity").toString();
        connectionCfg["DataBits"] = connObj.value("DataBits").toInt();
        connectionCfg["StopBits"] = connObj.value("StopBits").toInt();
    } else if (connectionCfg["Type"].toString() == "TCP") {
        connectionCfg["Port"] = connObj.value("Port").toInt();
    }

    // Load registers configuration
    registersCfg.clear();
    QJsonArray regArray = config.value("Registers").toArray();
    for (const QJsonValue &regVal : regArray) {
        QJsonObject regObj = regVal.toObject();
        QString regName = regObj.value("Name").toString();
        registersCfg[regName]["Type"] = regObj.value("Type").toString();
        registersCfg[regName]["Address"] = regObj.value("Address").toInt();
        registersCfg[regName]["UnitID"] = regObj.value("UnitID").toInt();
    }

    return true;
}

bool ModbusClientHandle::connectToDevice()
{
    dataMapOld.clear();
    firstRead = false;
    if (modbusDevice) {
        disconnectToDevice();
    }

    if (connectionCfg.isEmpty()) {
        emit ModbusStateChanged(0, "No connection configuration.");
        return false;
    }

    // Create appropriate modbus device based on connection type
    if (connectionCfg["Type"].toString() == "RTU") {
        modbusDevice = new QModbusRtuSerialClient(this);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, connectionCfg["IP"].toString());
        modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, stringToParity(connectionCfg["Parity"].toString()));
        modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, connectionCfg["BaudRate"].toInt());
        modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, connectionCfg["DataBits"].toInt());
        modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, connectionCfg["StopBits"].toInt());

    } else if (connectionCfg["Type"].toString() == "TCP") {
        modbusDevice = new QModbusTcpClient(this);
        modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, connectionCfg["IP"].toString());
        modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, connectionCfg["Port"].toInt());
    } else {
        emit ModbusStateChanged(0, "Unknown connection type.");
        return false;
    }

    modbusDevice->setTimeout(connectionCfg["Timeout"].toInt());
    modbusDevice->setNumberOfRetries(connectionCfg["Retries"].toInt());

    // Connect signals
    connect(modbusDevice, &QModbusClient::stateChanged, this, [this](QModbusDevice::State state) {
        QString stateStr;
        switch (state) {
        case QModbusDevice::UnconnectedState:
            stateStr = "Disconnected";
            break;
        case QModbusDevice::ConnectingState:
            stateStr = "Connecting...";
            break;
        case QModbusDevice::ConnectedState:
            stateStr = "Connected";
            break;
        default:
            stateStr = "Unknown state";
            break;
        }
        emit ModbusStateChanged(state, stateStr);
    });

    connect(modbusDevice, &QModbusClient::errorOccurred, this, [this](QModbusDevice::Error) {
        emit ModbusStateChanged(0, modbusDevice->errorString());;
    });

    if (modbusDevice->connectDevice()) {
        return true;
    } else {
        emit ModbusStateChanged(0, "Failed to connect to device.");
        return false;
    }
}

bool ModbusClientHandle::disconnectToDevice()
{
    if (modbusDevice) {
        modbusDevice->disconnectDevice();
        delete modbusDevice;
        modbusDevice = nullptr;
    }
    return true;
}

void ModbusClientHandle::readModbusValues()
{
    if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState) {
        emit ModbusStateChanged(0, "Not connected to device.");
        return;
    }

    dataMap.clear();
    pendingReadRequests = registersCfg.size();

    for (const QString &regName : registersCfg.keys()) {
        QModbusDataUnit::RegisterType type = getRegisterType(registersCfg[regName]["Type"].toString());
        int address = registersCfg[regName]["Address"].toInt();
        int unitID = registersCfg[regName]["UnitID"].toInt();

        QModbusDataUnit request(type, address, 1);
        QModbusReply *reply = modbusDevice->sendReadRequest(request, unitID);
        if (!reply) {
            emit ModbusStateChanged(0, "Read request error: " + modbusDevice->errorString());

        } else {
            connect(reply, &QModbusReply::finished, this, [this, reply, regName]() {
                onReadReply(reply, regName);
            });
        }
    }
}

void ModbusClientHandle::writeModbusValues(const QMap<QString, QVariant> &wDataMap)
{
    if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState) {
        emit ModbusStateChanged(0, "Not connected to device.");
        return;
    }

    pendingWriteRequests = wDataMap.size();

    for (const QString &regName : wDataMap.keys()) {
        if (!registersCfg.contains(regName)) {
            emit writeCompleted(false, regName, "Unknown register name.");
            continue;
        }

        QModbusDataUnit::RegisterType type = getRegisterType(registersCfg[regName]["Type"].toString());
        int address = registersCfg[regName]["Address"].toInt();
        int unitID = registersCfg[regName]["UnitID"].toInt();
        QVariant value = wDataMap.value(regName);

        QModbusDataUnit request(type, address, 1);
        request.setValue(0, value.toInt());

        QModbusReply *reply = modbusDevice->sendWriteRequest(request, unitID);
        if (!reply) {
            emit writeCompleted(false, regName, "Write request error: " + modbusDevice->errorString());

        } else {
            connect(reply, &QModbusReply::finished, this, [this, reply, regName]() {
                onWriteReply(reply, regName);
            });
        }
    }
}

void ModbusClientHandle::onReadReply(QModbusReply *reply, const QString &registerName)
{
    if (reply->error() == QModbusDevice::NoError) {
        const QModbusDataUnit &data = reply->result();
        if (data.isValid()) {
            dataMap[registerName] = data.value(0);
            emit readCompleted(registerName,data.value(0));//read one
            if (firstRead = false) {//第一次读取，更新dataMapOld
                dataMapOld[registerName] = data.value(0);
                emit readCompletedChanged(registerName,data.value(0));//read one
            } else {//不是第一次读
                if (dataMapOld[registerName] != data.value(0)) {//数据有变化时
                    dataMapOld[registerName] = data.value(0);
                    emit readCompletedChanged(registerName,data.value(0));//read one
                }
            }
        } else {
            emit ModbusStateChanged(0, "No data received for register: " + registerName);
        }
    } else {
        emit ModbusStateChanged(0, "Read error: " + reply->errorString());
    }

    reply->deleteLater();
    pendingReadRequests--;
    if (pendingReadRequests == 0) {
        emit allReadCompleted(dataMap);//read all
        if (firstRead = false) {//第一次读取，更新dataMapOld
            firstRead =true;
        }
    }
}

void ModbusClientHandle::onWriteReply(QModbusReply *reply, const QString &registerName)
{
    if (reply->error() == QModbusDevice::NoError) {
        emit writeCompleted(true, registerName);
    } else {
        emit writeCompleted(false, registerName, reply->errorString());
    }

    reply->deleteLater();
    pendingWriteRequests--;
    if (pendingWriteRequests == 0) {
        emit allWritesCompleted();
    }
}

QModbusDataUnit::RegisterType ModbusClientHandle::getRegisterType(const QString &type)
{
    if (type == "DiscreteInput") {
        return QModbusDataUnit::DiscreteInputs;
    } else if (type == "Coil") {
        return QModbusDataUnit::Coils;
    } else if (type == "InputRegister") {
        return QModbusDataUnit::InputRegisters;
    } else if (type == "HoldingRegister") {
        return QModbusDataUnit::HoldingRegisters;
    } else {
        return QModbusDataUnit::Invalid;
    }
}

QSerialPort::Parity ModbusClientHandle::stringToParity(const QString &parityStr)
{
    if (parityStr == "NoParity")
        return QSerialPort::NoParity;
    else if (parityStr == "EvenParity")
        return QSerialPort::EvenParity;
    else if (parityStr == "OddParity")
        return QSerialPort::OddParity;
    else if (parityStr == "SpaceParity")
        return QSerialPort::SpaceParity;
    else if (parityStr == "MarkParity")
        return QSerialPort::MarkParity;
    else
        return QSerialPort::NoParity;
}


void ModbusClientHandle::loadConfigSlot(const QString &configFilePath){
    loadConfig(configFilePath);
};//普通方法的槽形式，方便跨线程使用


void ModbusClientHandle::writeModbusValuesSlot(const QMap<QString, QVariant> &wDataMap){
    writeModbusValues(wDataMap);

};//普通方法的槽形式，方便跨线程使用

void ModbusClientHandle::readModbusValuesSlot(){
    readModbusValues();
};//普通方法的槽形式，方便跨线程使用

void ModbusClientHandle::disconnectToDeviceSlot(){
    disconnectToDevice();
};//普通方法的槽形式，方便跨线程使用

void ModbusClientHandle::connectToDeviceSlot(){
    connectToDevice();
};//普通方法的槽形式，方便跨线程使用









//这个要在最后一行 手动生成
#include "XYmodbus.moc"
